﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Drawing;
using System.Timers;

/* Copyright (c) 2014
 * author: Ricky Kratochwil
 * @ date: 2014-05-16
 * @ summary: This class handles all map related operations
*/

namespace Engine
{
	class CWorld
	{
		private string	m_strName;

		//The grid needs to be divisible by 2 on its x axis for gameplay balance 
		private CBrick[,]	m_iGrid;
		private CCoin[,]	m_iCoinGrid;
		private CPowerUp[,]	m_oPowerUpGrid;
		private int			m_iRemainingCoins;		//Could be calculated on demand but is more efficient not to
		
		private System.Timers.Timer m_oRandTimer;
		private Random				m_oRandGen;

		public CWorld(string strName)
		{
			m_strName	= strName;
			CLog.Log("Create new CWold object");

			CLog.Log("...init grid");
			m_iGrid				= new CBrick[Const.GRIDS_XAXIS, Const.GRIDS_YAXIS];
			m_iCoinGrid			= new CCoin[Const.GRIDS_XAXIS, Const.GRIDS_YAXIS];
			m_oPowerUpGrid		= new CPowerUp[Const.GRIDS_XAXIS, Const.GRIDS_YAXIS];
			m_iRemainingCoins	= 0;

			//No world should ever contain a null grid, so it can be used for world creation
			CLog.Log("...set grid to default");
			for(int iY = 0; iY < Const.GRIDS_YAXIS; iY++)
			{
				for(int iX = 0; iX < Const.GRIDS_XAXIS; iX++)
					m_iGrid[iX, iY] = new CBrick();
			}

			//Init random
			m_oRandGen = new Random();
		}


		/* Serves as an destructor
		 *  @ requires:	
		 *      -/-
		 *  @ modifies:	
		 *      -/-
		 *  @ ensures:	
		 *     -/-
		 *  @ remarks:	
		 *     -/-
		 *  @ author / date:
		 *     - KR / 2014.05.19
		   */
		public void Destroy()
		{
			CLog.Log("Destroy CWorld");
			for(int iY = 0; iY < Const.GRIDS_YAXIS; iY++)
			{
				for(int iX = 0; iX < Const.GRIDS_XAXIS; iX++)
				{
					m_iGrid[iX, iY].Destroy();
				}
			}
		}


		/* Gives access to the grid dimensions, this meight be needed for drawing
		 *  @ requires:	
		 *      -/-
		 *  @ modifies:	
		 *      - Parameters will be used as return values
		 *  @ ensures:	
		 *     -/-
		 *  @ remarks:	
		 *     -/-
		 *  @ author / date:
		 *     - KR / 2014.05.16
		*/
		public void GetWorldDimensions(ref int iWidth, ref int iHeight)
		{
			iWidth  = Const.GRIDS_XAXIS * Const.GRID_DIM;
			iHeight = Const.GRIDS_YAXIS * Const.GRID_DIM;
		}


		/* Get world name
		 *  @ requires:	
		 *      -/-
		 *  @ modifies:	
		 *      -/-
		 *  @ ensures:	
		 *     -/-
		 *  @ remarks:	
		 *     -/-
		 *  @ author / date:
		 *     - KR / 2014.05.16
		*/
		public string GetWorldName()
		{
			return m_strName;
		}


		/* Set brick in CBrick array to specific type
		 *  @ requires:	
		 *      -/-
		 *  @ modifies:	
		 *      -/-
		 *  @ ensures:	
		 *     -/-
		 *  @ remarks:	
		 *     -/-
		 *  @ author / date:
		 *     - KR / 2014.05.18
		*/
		public void SetBrick(int iBrickType, int iX, int iY)
		{
			//No need setting the same type again
//			if(m_iGrid[iX, iY].GetBrickType() == iBrickType)
//				return;

			CBrick oBrickLeft, oBrickRight;

			oBrickLeft		= GetBrick(iX-1, iY);
			oBrickRight		= GetBrick(iX+1, iY);

			if(iBrickType == Const.BLOCK_ICE)
			{
				m_iGrid[iX, iY] = new CBrick();

				//Simple workaround to deletion: Just pretend to be inserting the same brick right and left
				if(oBrickLeft != null && oBrickLeft.GetBrickType() != Const.BLOCK_ICE)
					SetBrick(oBrickLeft.GetBrickType(), iX-1, iY);
				if(oBrickRight != null && oBrickRight.GetBrickType() != Const.BLOCK_ICE)
					SetBrick(oBrickRight.GetBrickType(), iX+1, iY);

				return;
			}

			if(iBrickType == Const.BLOCK_SPIKES)
			{
				if(m_iGrid[iX, iY] == null || m_iGrid[iX, iY].GetBrickType() != Const.BLOCK_SPIKES)
					m_iGrid[iX, iY] = new CBrick(Const.BLOCK_SPIKES, Const.BRICK_ALIGN_SINGLE);
				return;
			}


			//Single brick
			if((oBrickRight == null || oBrickRight.GetBrickType() != iBrickType) && (oBrickLeft == null || oBrickLeft.GetBrickType() != iBrickType))
			{
				CreateBrick(iBrickType, iX, iY, Const.BRICK_ALIGN_SINGLE);
				return;
			}


			//Go for recursion
			if((oBrickLeft != null && oBrickLeft.GetBrickType() != iBrickType) || oBrickLeft == null)		//Right way first, since click was left resp. left is invalid
			{
				SetBrick(iBrickType, iX, iY, false);
				SetBrick(iBrickType, iX, iY, true);
			}
			else if((oBrickRight != null && oBrickRight.GetBrickType() != iBrickType) || oBrickRight == null)	//Left way first, since click was right resp. right is invalid
			{
				SetBrick(iBrickType, iX, iY, true);
				SetBrick(iBrickType, iX, iY, false);
			}
			else	//Close island (both right and left are valid and of same kind (remark: unlike the others, the order of SetbBick can be changed)
			{
				SetBrick(iBrickType, iX, iY, false);
				SetBrick(iBrickType, iX, iY, true);				
			}
		}


		//Recursive helper for brick insertion
		//@ KR / 2014.05.20
		private void SetBrick(int iBrickType, int iX, int iY, bool bLeft, bool bSkip = false)
		{
			CBrick oBrickNext;

			if(bLeft)	// Left direction
			{
				oBrickNext = GetBrick(iX-1, iY);
				if(oBrickNext == null || oBrickNext.GetBrickType() != iBrickType)	//End brick
				{
					CreateBrick(iBrickType, iX, iY, Const.BRICK_ALIGN_LEFT);
					return;
				}
				
				CreateBrick(iBrickType, iX, iY, Const.BRICK_ALIGN_CONT);		//Continuous brick	
				SetBrick(iBrickType, --iX, iY, true);
			}

			if(!bLeft)	
			{
				if(bSkip)
				{
					SetBrick(iBrickType, ++iX, iY, false);
					return;
				}

				oBrickNext = GetBrick(iX+1, iY);
				if(oBrickNext == null || oBrickNext.GetBrickType() != iBrickType)	//End brick
				{
					CreateBrick(iBrickType, iX, iY, Const.BRICK_ALIGN_RIGHT);
					return;
				}
				
				CreateBrick(iBrickType, iX, iY, Const.BRICK_ALIGN_CONT);		//Continuous brick	
				SetBrick(iBrickType, ++iX, iY, false);
			}
		}

		//Actual brick setter
		//@ KR / 2014.05.20
		private void CreateBrick(int iBrickType, int iX, int iY, int iBrickAlign = 0)
		{
			switch(iBrickType)	//Continuous brick
			{
				case Const.BLOCK_ICE :	//No purpose for iBrickAlign
					m_iGrid[iX, iY] = new CBrick();
					break;
				case Const.BLOCK_ROCK :
					if(m_iGrid[iX, iY].GetBrickDamaged())
					{
						m_iGrid[iX, iY] = new CRock(iBrickAlign);
						m_iGrid[iX, iY].SetDamaged();
					}
					else
						m_iGrid[iX, iY] = new CRock(iBrickAlign);
					break;
			}
		}

		//Damage stone brick to sustain only 1 fall
		//@ KR / 2014.07.09
		public void DamageBrick(int iX, int iY)
		{
			m_iGrid[iX, iY].SetDamaged();
		}

		//Create utils
		//@ KR / 2014.06.26
		public void SetUtil(int iUtilType, int iX, int iY, bool bPerma, int iValue = 0)
		{
			switch(iUtilType)
			{
				case Const.UTIL_COIN :
					CreateCoin(iX, iY);
					break;
				default :
					CreatePowerUp(iX, iY, iUtilType, bPerma, iValue);
					break;
			}
		}

//////////////////////////////////////////////////////////////////////////////////////////////////////
////////// COIN/DIAMOND BASED OPS, NEEDED BY PLAYER TO GATHER POINTS

		//Create coin and alloc in array
		//@ KR / 2014.06.24
		private void CreateCoin(int iX, int iY)
		{
			if(m_iCoinGrid[iX, iY] == null)
			{
				m_iCoinGrid[iX, iY] = new CCoin(iX, iY);
				m_iRemainingCoins++;
			}
		}

		//Get Coin or null at pos
		//@ KR / 2014.06.24
		public CCoin GetCoin(int iX, int iY)
		{
			return m_iCoinGrid[iX, iY];
		}

		//Get remaining coins on world left to collect
		//@ KR / 2014.06.29
		public int GetRemainingCoins()
		{
			return m_iRemainingCoins;
		}

		//Remove Coin at pos (must be called after collecting coin)
		// - Calls Coin to destroy itself
		//@ KR / 2014.06.25
		public void RemoveCoin(int iX, int iY)
		{
			if(m_iCoinGrid[iX, iY] != null)
			{
				m_iRemainingCoins--;

				m_iCoinGrid[iX, iY].Destroy();
				m_iCoinGrid[iX, iY] = null;
			}
		}

//////////////////////////////////////////////////////////////////////////////////////////////////////
////////// POWER UPS

		//Create new power up
		//@ KR / 2014.07.09
		private bool CreatePowerUp(int iX, int iY, int iType, bool bPerma, int iValue = 0)
		{
			if(m_oPowerUpGrid[iX, iY] == null)
			{
				switch(iType)
				{
					case Const.UTIL_DIAMOND :
						if(iValue == 0)
							m_oPowerUpGrid[iX, iY] = new CPowerUpDiamond(iX, iY);
						else
							m_oPowerUpGrid[iX, iY] = new CPowerUpDiamond(iX, iY, iValue);
						break;
					case Const.UTIL_STAMINA :
						m_oPowerUpGrid[iX, iY] = new CPowerUpStamina(iX, iY, bPerma);
						break;
					case Const.UTIL_TELEPORT :
						m_oPowerUpGrid[iX, iY] = new CPowerUpTeleport(iX, iY, bPerma);
						break;
				}
				return true;
			}
			return false;
		}

		//Remove power up
		//@ KR / 2014.07.09
		public void RemovePowerUp(int iX, int iY)
		{
			if(m_oPowerUpGrid[iX, iY] != null)
			{
				m_oPowerUpGrid[iX, iY].Destroy();
				m_oPowerUpGrid[iX, iY] = null;
			}
		}

		//Get power up
		//@ KR / 2014.07.09
		public CPowerUp GetPowerUp(int iX, int iY)
		{
			return m_oPowerUpGrid[iX, iY];
		}

//////////////////////////////////////////////////////////////////////////////////////////////////////


		/* Get Brick at pos
		 *  @ requires:	
		 *     -/-
		 *  @ modifies:	
		 *     -/-
		 *  @ ensures:	
		 *     -/-
		 *  @ remarks:	
		 *     -/-
		 *  @ author / date:
		 *     - KR / 2014.05.19
		*/
		private CBrick GetBrick(int iX, int iY)
		{
			if(iX < 0 || iX >= Const.GRIDS_XAXIS || iY < 0 || iY >= Const.GRIDS_YAXIS)
				return null;

			return m_iGrid[iX, iY];
		}

		//Check if brick standing on is damaged
		//@ KR / 2014.07.09
		public bool IsStandBrickDamaged(int iX, int iY)
		{
			if(iY != Const.GRIDS_YAXIS - 1)
				return m_iGrid[iX, iY+1].GetBrickDamaged();
			return false;
		}

		//Check if position is possible free
		//@ KR / 2014.07.09
		public bool PosFree(int iX, int iY)
		{
			if(GetPowerUp(iX, iY) == null && GetCoin(iX, iY) == null && GetBrick(iX, iY).GetBrickType() == Const.BLOCK_ICE)
				return true;
			return false;
		}

//////////////////////////////////////////////////////////////////////////////////////////////////////
////////// GET CONDITION OF BRICKS (SOLID | WALK THROUGH | SPIKES)

		//Get solid stand at position
		// -> Stand being the brick below is solid
		//@ KR / 2014.05.20
		public bool IsSolidStand(int iX, int iY)
		{
			if(iY == Const.GRIDS_YAXIS - 1)
				return true;

			return IsSolid(iX, ++iY);
		}

		//Get solid ceiling at position
		// -> ceiling being the brick above is solid
		//@ KR / 2014.05.20
		public bool IsSolidCeiling(int iX, int iY)
		{
			if(iY == 0)
				return true;

			return IsSolid(iX, --iY);
		}

		//Get solid state at pos (global)
		// - Does not ensure iY is within range, to check above and below use the resp. functions
		//@ KR / 2014.06.24
		public bool IsSolid(int iX, int iY)
		{
			if(iX == -1 || iX == Const.GRIDS_XAXIS)
				return true;

			return GetBrick(iX, iY).GetSolid();
		}

		//Get spikes state at pos
		//@ KR / 2014.06.24
		public bool IsOverSpikes(int iX, int iY)
		{
			if(iY == Const.GRIDS_YAXIS - 1)
				return false;

			return GetBrick(iX, ++iY).GetBrickType() == Const.BLOCK_SPIKES;
		}

//////////////////////////////////////////////////////////////////////////////////////////////////////
////////// ANIMATION RELATED

		//Get number of animated objects on world
		//@ KR / 2014.07.08
		public int GetAnimatedCount()
		{
			int iCount = 0;

			foreach(CCoin element in m_iCoinGrid)
			{
				if(element != null)
					iCount++;
			}

			foreach(CPowerUp element in m_oPowerUpGrid)
			{
				if(element != null)
					iCount++;
			}

			return iCount;
		}

//////////////////////////////////////////////////////////////////////////////////////////////////////
////////// RANDOM EVENTS

		//Start random generator
		// - Random events will fire in between 20 and 60 seconds
		//@ KR / 2014.07.09
		public void SetRandom(bool bValue)
		{
			if(bValue)
			{
				m_oRandTimer          = new System.Timers.Timer(m_oRandGen.Next(20000,60000));
//				m_oRandTimer          = new System.Timers.Timer(m_oRandGen.Next(1000,1000));
				m_oRandTimer.Elapsed += new ElapsedEventHandler(RandomTimeEvent);
				m_oRandTimer.Start();
			}
			else
			{
				m_oRandTimer.Stop();
			}
		}

		//Timer for random events
		//@ KR / 2014.07.09
		private void RandomTimeEvent(object source, ElapsedEventArgs e)
		{
			m_oRandTimer.Stop();

			if(CPowerUp.GetCount() != 8)	//Only 5 PowerUps at any time
			{
				int iX, iY;

				do	//Generate random pos that is not occupied
				{
					iX = m_oRandGen.Next(Const.GRIDS_XAXIS);
					iY = m_oRandGen.Next(Const.GRIDS_YAXIS);
				}
				while(GetBrick(iX, iY).GetBrickType() != Const.BLOCK_ICE || GetCoin(iX, iY) != null || GetPowerUp(iX, iY) != null);

				switch(m_oRandGen.Next(3))
				{
					case 0 :	//Generate diamond
						if(CreatePowerUp(iX, iY, Const.UTIL_DIAMOND, false))
							GetPowerUp(iX, iY).SetWorld(this);
						break;
					case 1 :	//Generate stamina
						if(CreatePowerUp(iX, iY, Const.UTIL_STAMINA, false))
							GetPowerUp(iX, iY).SetWorld(this);
						break;
					case 2 :	//Generate teleport
						if(CreatePowerUp(iX, iY, Const.UTIL_TELEPORT, false))
							GetPowerUp(iX, iY).SetWorld(this);
						break;
				}
			}

			//Restart timer
			SetRandom(true);
		}


//////////////////////////////////////////////////////////////////////////////////////////////////////

		/* Return brick bitmap for drawing ops on point
		 *  @ requires:	
		 *      -/-
		 *  @ modifies:	
		 *      -/-
		 *  @ ensures:	
		 *     -/-
		 *  @ remarks:	
		 *     -/-
		 *  @ author / date:
		 *     - KR / 2014.05.19
		   */
		public Bitmap GetBrickBitmap(int iX, int iY)
		{
			return GetBrick(iX, iY).GetBrickBitmap();
		}


		/* Return background bitmap for drawing ops
		 *  @ requires:	
		 *      -/-
		 *  @ modifies:	
		 *      -/-
		 *  @ ensures:	
		 *     -/-
		 *  @ remarks:	
		 *     -/-
		 *  @ author / date:
		 *     - KR / 2014.05.19
		 */
		public Bitmap GetWorldBitmap()
		{
			Bitmap		oBrickImage;
			Bitmap		bmpPlayboard;
			int			iWidth	= 0;// = Const.GRIDS_XAXIS*Const.GRID_DIM;
			int			iHeight	= 0;// = Const.GRIDS_YAXIS*Const.GRID_DIM;
			Graphics	g;

			GetWorldDimensions(ref iWidth, ref iHeight);

			bmpPlayboard = new Bitmap(iWidth, iHeight);
			g			 = Graphics.FromImage(bmpPlayboard);

			g.DrawImage(Properties.Resources.background, 0, 0, iWidth, iHeight);
			for(int iX = 0; iX < Const.GRIDS_XAXIS; iX++)
			{
				for(int iY = 0; iY < Const.GRIDS_YAXIS; iY++)
				{
					oBrickImage = GetBrickBitmap(iX, iY);

					if(oBrickImage != null)		//e.g. ice has no image
						g.DrawImage(oBrickImage, iX * Const.GRID_DIM, iY * Const.GRID_DIM, oBrickImage.Width, oBrickImage.Height);
				}
			}

			g.Dispose();
			return bmpPlayboard;
		}


		/* Get string representing the world object
		 *  @ requires:	
		 *     -/-
		 *  @ modifies:	
		 *     -/-
		 *  @ ensures:	
		 *     -/-
		 *  @ remarks:	
		 *     - Assuming serialization is not allowed
		 *  @ author / date:
		 *     - KR / 2014.05.19
		*/
		new public string ToString()
		{
			CBrick			oTempBrick;
			int				iBrickType;
			StringBuilder	strContent = new StringBuilder();

			for(int iY = 0; iY < Const.GRIDS_YAXIS; iY++)
			{
				for(int iX = 0; iX < Const.GRIDS_XAXIS; iX++)
				{
					oTempBrick = GetBrick(iX, iY);
					
					//Save brick
					iBrickType = oTempBrick.GetBrickType();

					if(iBrickType != Const.BLOCK_ICE)		//Meight be util?
					{
						if(oTempBrick.GetBrickDamaged())
							strContent.Append(Const.BLOCK_ROCK_DAMAGE.ToString());
						else
							strContent.Append(iBrickType.ToString());
					}
					else
					{
						if(GetCoin(iX, iY) != null)		//Save coin
							strContent.Append(Const.UTIL_COIN.ToString());
						else if(GetPowerUp(iX, iY) != null)
							strContent.Append(GetPowerUp(iX, iY).GetPowerUpType());
						else
							strContent.Append(iBrickType.ToString());
					}
				}
				strContent.Append("\n");
			}

			return strContent.ToString() + "\n";
		}
	}
}
